﻿using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Zhaoxi.MQTT.ProtocolTest
{
    internal class Program
    {
        static Random rnd = new Random();
        static void Main(string[] args)
        {
            Console.WriteLine("欢迎大家参与朝夕教育上位机课程!");

            {
                byte[] bs = LengthEncode(0);
                bs = LengthEncode(95);
                bs = LengthEncode(127);
                bs = LengthEncode(128);
                bs = LengthEncode(129);
                bs = LengthEncode(16_383);
                bs = LengthEncode(16_384);
                bs = LengthEncode(16_385);
                bs = LengthEncode(2_097_151);
                bs = LengthEncode(2_097_152);
                bs = LengthEncode(2_097_153);
                bs = LengthEncode(268_435_455);
                bs = LengthEncode(20000);

                var v = LengthDecode(bs);
                v = LengthDecode(new byte[] { 0xA0, 0x9C, 0x01 });
            }



            // 体现出当前协议是以TCP为基础的应用层协议 
            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socket.Connect("127.0.0.1", 1883);// 连接到服务器

            // 前面 socket.Connect("127.0.0.1", 1883); 之后必须是 Connect请求消息
            // 如果不是，服务端断掉连接

            // 连接请求
            ConnectReq(socket);

            // 心跳
            //PingReq(socket);

            // 订阅
            //Subscribte(socket);

            // 取消订阅
            //Unsubscribte(socket);

            // 发布
            Publish_0(socket);
            //Publish_1(socket);
            //Publish_2(socket);

            // 断开连接（直接退出与断开请求  两种不同的结果）
            // 如果调用 Disconnect 服务端不会发布遗产
            Disconnect(socket);

            Console.ReadLine();
        }
        private static void Disconnect(Socket socket)
        {
            List<byte> reqBytes = new List<byte>();
            byte byte1 = 14 << 4;
            reqBytes.Add(byte1);
            // 剩余字节长度
            reqBytes.Add(0);

            socket.Send(reqBytes.ToArray()); ;
        }
        private static void Publish_2(Socket socket)
        {
            List<byte> reqBytes = new List<byte>();
            byte byte1 = 3 << 4;// 功能码
            // 如里需要服务端进行消息的保存，将bit0置1
            //byte1 |= 1;
            // 如果需要设备发存消息的QoS等级，设置bit1 - 2两个位，最大为2
            int qos = 2;
            byte1 |= (byte)(qos << 1);
            // 如果消息是重发的，设备bit3为1

            reqBytes.Add(byte1);
            ushort pid = (ushort)rnd.Next(100, 1000);

            // 可变报头+有效载荷
            List<byte> bytes = new List<byte>();
            {
                // 添加主题
                string topic = "t001";
                byte[] t_bytes = Encoding.ASCII.GetBytes(topic);
                bytes.Add((byte)(t_bytes.Length / 256));
                bytes.Add((byte)(t_bytes.Length % 256));
                bytes.AddRange(t_bytes);

                // 添加PID
                bytes.Add((byte)(pid / 256));
                bytes.Add((byte)(pid % 256));

                // 添加消息内容
                string msg = "Hello MQTT - 2";
                byte[] m_bytes = Encoding.UTF8.GetBytes(msg);
                bytes.AddRange(m_bytes);
            }

            reqBytes.AddRange(LengthEncode(bytes.Count));
            reqBytes.AddRange(bytes);

            SendAndReceive(reqBytes.ToArray(), socket);// 接收PubRec响应报文   功能码：5

            // 发送PubRel报文 6
            {
                reqBytes = new List<byte>();
                byte1 = 6 << 4;// 功能码
                byte1 |= 2;
                reqBytes.Add(byte1);
                // 剩余字节长度  2
                reqBytes.Add(2);
                // 添加pid
                reqBytes.Add((byte)(pid / 256));
                reqBytes.Add((byte)(pid % 256));

                SendAndReceive(reqBytes.ToArray(), socket);// 接收PubComp响应报文   功能：7
            }
        }

        private static void Publish_1(Socket socket)
        {
            List<byte> reqBytes = new List<byte>();
            byte byte1 = 3 << 4;// 功能码
            // 如里需要服务端进行消息的保存，将bit0置1
            //byte1 |= 1;
            // 如果需要设备发存消息的QoS等级，设置bit1 - 2两个位，最大为2
            int qos = 1;
            byte1 |= (byte)(qos << 1);
            // 如果消息是重发的，设备bit3为1

            reqBytes.Add(byte1);

            // 可变报头+有效载荷
            List<byte> bytes = new List<byte>();
            {
                // 添加主题
                string topic = "t001";
                byte[] t_bytes = Encoding.ASCII.GetBytes(topic);
                bytes.Add((byte)(t_bytes.Length / 256));
                bytes.Add((byte)(t_bytes.Length % 256));
                bytes.AddRange(t_bytes);

                // 添加PID
                ushort pid = (ushort)rnd.Next(100, 1000);
                bytes.Add((byte)(pid / 256));
                bytes.Add((byte)(pid % 256));

                // 添加消息内容-
                string msg = "Hello MQTT - 1";
                byte[] m_bytes = Encoding.UTF8.GetBytes(msg);
                bytes.AddRange(m_bytes);
            }

            reqBytes.AddRange(LengthEncode(bytes.Count));
            reqBytes.AddRange(bytes);

            SendAndReceive(reqBytes.ToArray(), socket); // 接收PubAck的响应 功能码：4
        }


        private static void Publish_0(Socket socket)
        {
            List<byte> reqBytes = new List<byte>();
            byte byte1 = 3 << 4;// 功能码
            // 如里需要服务端进行消息的保存，将bit0置1   RETAIN标记
            //byte1 |= 1;
            // 如果需要设备发存消息的QoS等级，设置bit1 - 2两个位，最大为2
            int qos = 0;
            byte1 |= (byte)(qos << 1);
            // 如果消息是重发的，设备bit3为1

            reqBytes.Add(byte1);

            // 可变报头+有效载荷
            List<byte> bytes = new List<byte>();
            {
                // 添加主题
                string topic = "t001";
                byte[] t_bytes = Encoding.ASCII.GetBytes(topic);
                bytes.Add((byte)(t_bytes.Length / 256));
                bytes.Add((byte)(t_bytes.Length % 256));
                bytes.AddRange(t_bytes);

                // 添加消息内容
                string msg = "Hello MQTT - 0";
                byte[] m_bytes = Encoding.UTF8.GetBytes(msg);
                bytes.AddRange(m_bytes);
            }

            reqBytes.AddRange(LengthEncode(bytes.Count));
            reqBytes.AddRange(bytes);

            socket.Send(reqBytes.ToArray());
        }

        private static void Unsubscribte(Socket socket)
        {
            List<byte> reqBytes = new List<byte>();
            byte byte1 = 10 << 4;
            byte1 |= 2;// 固定写法，字节的bit1置1  
            reqBytes.Add(byte1);

            // 可变报头+有效载荷
            List<byte> bytes = new List<byte>();
            {
                // 可变报头(报文标识符  2byte)
                ushort pid = (ushort)rnd.Next(100, 1000);
                bytes.Add((byte)(pid / 256));
                bytes.Add((byte)(pid % 256));

                string topic = "a/b";
                byte[] t_bytes = Encoding.ASCII.GetBytes(topic);
                bytes.Add((byte)(t_bytes.Length / 256));
                bytes.Add((byte)(t_bytes.Length % 256));
                bytes.AddRange(t_bytes);

                topic = "z/x";
                t_bytes = Encoding.ASCII.GetBytes(topic);
                bytes.Add((byte)(t_bytes.Length / 256));
                bytes.Add((byte)(t_bytes.Length % 256));
                bytes.AddRange(t_bytes);
            }

            reqBytes.AddRange(LengthEncode(bytes.Count));
            reqBytes.AddRange(bytes);

            SendAndReceive(reqBytes.ToArray(), socket);
        }

        private static void Subscribte(Socket socket)
        {
            List<byte> reqBytes = new List<byte>();

            // 固定报头
            byte byte1 = 8 << 4;
            // 位运算
            // 将8这个值左移4位？
            // 0000 1000   ->  1000 0000
            // 13 ->   1101 0000  
            byte1 |= 2;// 固定写法，字节的bit1置1  
            //  | 或运算    & 与运算
            // 1000 0000
            // 0000 0010
            // 1000 0010 -> 或运算结果
            reqBytes.Add(byte1);

            List<byte> bytes = new List<byte>();
            // 可变报头(报文标识符  2byte)
            ushort pid = (ushort)rnd.Next(100, 1000);
            bytes.Add((byte)(pid / 256));
            bytes.Add((byte)(pid % 256));

            // 载荷信息
            // 订阅主题名称
            string topic = "a/b";    // "a/#"
            byte[] t_bytes = Encoding.ASCII.GetBytes(topic);
            bytes.Add((byte)(t_bytes.Length / 256));
            bytes.Add((byte)(t_bytes.Length % 256));
            bytes.AddRange(t_bytes);
            // QoS  服务质量(0,1,2)   不能超过2
            // 0:最多发送成功一次
            // 1:最少发送成功一次
            // 2:确保发送成功一次
            bytes.Add(1);

            topic = "a/d";
            t_bytes = Encoding.ASCII.GetBytes(topic);
            bytes.Add((byte)(t_bytes.Length / 256));
            bytes.Add((byte)(t_bytes.Length % 256));
            bytes.AddRange(t_bytes);
            // QoS 2
            bytes.Add(2);

            topic = "a/f";
            t_bytes = Encoding.ASCII.GetBytes(topic);
            bytes.Add((byte)(t_bytes.Length / 256));
            bytes.Add((byte)(t_bytes.Length % 256));
            bytes.AddRange(t_bytes);
            // QoS 0
            bytes.Add(0);


            // 添加报文长度   最大256M
            reqBytes.AddRange(LengthEncode(bytes.Count));
            reqBytes.AddRange(bytes);

            //socket.Send(reqBytes.ToArray());
            SendAndReceive(reqBytes.ToArray(), socket);


        }

        private static void PingReq(Socket socket)
        {
            Task.Run(async () =>
            {
                while (true)
                {
                    await Task.Delay(1000);
                    // 心跳请求，只有固定报头
                    List<byte> ping_bytes = new List<byte>();
                    byte byte1 = 12 << 4;// 消息类型  12
                    ping_bytes.Add(byte1);
                    ping_bytes.Add(0x00);

                    socket.Send(ping_bytes.ToArray());
                }
            });
        }

        private static void ConnectReq(Socket socket)
        {
            // Connect握手-》发送ClientID   用户名  密码
            List<byte> bytes = new List<byte>();
            //固定报头
            {
                byte byte1 = 1 << 4;// 直接写8？  发送      0011 1100
                _ = byte1 >> 4;//  弃元 C#7.0               解析      0011 0000
                               // 0001 0000   ->  0x10
                bytes.Add(byte1);
                // 
                // byte byte2 = 0;// 暂时无法确定   一个字节最多表示255个长度
                // 特殊  超出255字节
                // 1000 0000    1000 0000    1000 0000       127        
                // 最高位  标记位  如果是1   表示后面一个字节也是长度值
                // 如果是123个字节    7B     200                        300  

                // 1Byte    0 -127     0x00-0x7F
                // 2Byte    128-16383  0x80 0x01  - 0xFF 0x7F   
            }
            List<byte> byte_list = new List<byte>();
            // Connect的可变报头
            {
                // 协议信息
                string p_str = "MQTT";
                byte[] p_bytes = Encoding.ASCII.GetBytes(p_str);
                byte_list.Add((byte)(p_bytes.Length / 256));
                byte_list.Add((byte)(p_bytes.Length % 256));
                byte_list.AddRange(p_bytes);

                // 版本号  一个字节
                byte_list.Add(0x04);// 3.1.1版本协议，如果是5.0版本协议：0x05

                // 连接标志：一个字节
                {
                    byte flag = 0;
                    flag |= 128;// 将用户名标记置1
                    flag |= 64;// 将密码标记置1

                    // 遗嘱：
                    // （场景：客户端A连接时，建立遗嘱；异常断线，遗嘱有效
                    //         包含：主题名/数据   "offline"/"123"）
                    // 客户端B   订阅  offline
                    // 客户端断线
                    // 客户端B接收到A的遗嘱
                    // 客户端C上线，并订阅了offline      
                    flag |= 4;// 将使用遗嘱标记置1

                    // 清理会话
                    flag |= 2; // 将Clean Session标记置1

                    byte_list.Add(flag);
                }
                // Keep Alive    秒：
                // 服务端如果要判断标准：
                // 1、指客户端两次通信间隔时间
                // 2、100 * 1.5 时间范围
                ushort second = 100;
                byte_list.Add((byte)(second / 256));
                byte_list.Add((byte)(second % 256));
            }
            // 负载信息
            {
                // Client ID
                string client_id = "zx001";
                byte[] ci_bytes = Encoding.ASCII.GetBytes(client_id);
                byte_list.Add((byte)(ci_bytes.Length / 256));
                byte_list.Add((byte)(ci_bytes.Length % 256));
                byte_list.AddRange(ci_bytes);

                // 根据可变报头中的标记位，需要：
                // 添加遗嘱主题信息
                string will_topic_str = "offline";
                byte[] wt_bytes = Encoding.ASCII.GetBytes(will_topic_str);
                byte_list.Add((byte)(wt_bytes.Length / 256));
                byte_list.Add((byte)(wt_bytes.Length % 256));
                byte_list.AddRange(wt_bytes);
                // 添加遗嘱消息信息
                string will_msg_str = "AAA123";
                byte[] wm_bytes = Encoding.ASCII.GetBytes(will_msg_str);
                byte_list.Add((byte)(wm_bytes.Length / 256));
                byte_list.Add((byte)(wm_bytes.Length % 256));
                byte_list.AddRange(wm_bytes);

                // 添加用户名信息
                string user_str = "admin";
                byte[] u_bytes = Encoding.ASCII.GetBytes(user_str);
                byte_list.Add((byte)(u_bytes.Length / 256));
                byte_list.Add((byte)(u_bytes.Length % 256));
                byte_list.AddRange(u_bytes);
                // 添加密码信息
                string pwd_str = "123456";
                byte[] p_bytes = Encoding.ASCII.GetBytes(pwd_str);
                byte_list.Add((byte)(p_bytes.Length / 256));
                byte_list.Add((byte)(p_bytes.Length % 256));
                byte_list.AddRange(p_bytes);
            }

            // 拼接长度数据
            int len = byte_list.Count;
            byte[] len_bytes = LengthEncode(len);
            bytes.AddRange(len_bytes);// 长度信息
            bytes.AddRange(byte_list);// 可变报头+载荷信息

            //socket.Send(bytes.ToArray());// 发送MQTT的请求连接报文 
            SendAndReceive(bytes.ToArray(), socket);
        }

        static byte[] SendAndReceive(byte[] bytes, Socket socket)
        {
            socket.Send(bytes);

            List<byte> byteList = new List<byte>();
            byte[] respBytes = new byte[2];// 固定报头
            socket.Receive(respBytes, 0, 2, SocketFlags.None);

            byteList.AddRange(respBytes);
            for (int i = 0; i < 3; i++)
            {
                if ((byteList[byteList.Count - 1] & 0x80) != 0)
                {
                    respBytes = new byte[1];// 固定报头
                    socket.Receive(respBytes, 0, 1, SocketFlags.None);
                    byteList.Add(respBytes[0]);
                }
                else
                    break;
            }
            byte[] lenBytes = byteList.GetRange(1, byteList.Count - 1).ToArray();
            long len = LengthDecode(lenBytes);// 当前报文剩余字节的数量

            byte[] dataBytes = new byte[len];// 可变报头+有效载荷的字节
            socket.Receive(dataBytes, 0, dataBytes.Length, SocketFlags.None);
            byteList.AddRange(dataBytes);

            return byteList.ToArray();// 将完整响应报文返回 
        }

        // 从一个十进制数转换成字节
        static byte[] LengthEncode(int len)
        {
            List<byte> bytes = new List<byte>();
            ulong rc = 0;
            byte d;
            do
            {
                d = (byte)(len % 128);
                len /= 128;
                if (len > 0)
                    d |= 128;
                bytes.Add(d);
            } while (len > 0);
            return bytes.ToArray();
        }
        // 根据字节转换成数字
        static long LengthDecode(byte[] bytes)
        {
            byte encodeByte;
            uint multiplier = 1;
            long rc = 0;
            int i = 0;
            do
            {
                encodeByte = bytes[i++];
                rc += (encodeByte & 127) * multiplier;
                if (multiplier > 128 * 128 * 128)
                    break;
                else
                    multiplier *= 128;
            } while ((encodeByte & 128) != 0);
            return rc;
        }
    }
}
